#include "Lily_help.h"
#include <string.h>
#include <math.h>
#include "hashlib.h"

int32_t cmd_help(int32_t nargin, char **arg)
{

	Cmd p = li_cmds;
	char tx[TX_len];
	char *tx2 = tx;
	int32_t now;
	for (now = 1; now < nargin; now++)
	{
		char *item = NULL;
		item = arg[now];
		int32_t index = search_cmd_in_Lily_ui(item);
		if (index >= 0)
		{
			sprintf(tx, ">>%s: ", p[index].name);
			lily_out(tx);
			lily_out(p[index].annotation);
			lily_out("\n");
			continue;
		}
		index = search_fun_in_Lily_ui(item);
		if (index >= 0)
		{
			Fun r = li_funs;
			sprintf(tx, ">>%s:%d\n", r[index].name, r[index].narg);
			lily_out(tx);
			continue;
		}
		lily_out(">>not found help of:");
		lily_out(item);
		lily_out("\n");
		return -1;
	}

	if (nargin > 1)
		return 0;
	lily_out(">cmds:\n");
	int32_t i;
	for (i = 0; i < lily_ui.cmds->count; i++)
	{
		// if (p[i].isHidden)
		// {
		// 	*tx2++ = '_';
		// }
		// else
		sprintf(tx2, "%s\n", p[i].name);
		lily_out(tx);
	}
	Fun r = li_funs;
	lily_out(">funs:\n");
	// int32_t i;
	for (i = 0; i < lily_ui.funs->count; i++)
	{
		sprintf(tx, "%s\n", r[i].name);
		lily_out(tx);
	}
	Var q = li_vars;
	lily_out(">vars:\n");
	// int32_t i;
	for (i = 0; i < lily_ui.vars->count; i++)
	{
		sprintf(tx, "%s\n", q[i].name);
		lily_out(tx);
	}
	lily_out("<\n");
	return 0;
}

int32_t cmd_delete(int32_t n, char **arg)
{
	char user_do = 1;
	int32_t i = 1;
	if (n == 1)
	{
		i = 0;
		user_do = 0;
	}
	// if (n < 2 )return -1;
	for (; i < n; i++)
	{
		int32_t index = search_var_in_Lily_ui(arg[i]);
		if (index < 0)
		{
			int32_t f = remove_fun_by_str(arg[i]);
			if (f < 0)
				f = remove_cmd_by_str(arg[i]);
			if (f < 0)
			{
				lily_out(arg[i]);
				lily_out(":not found\n");
				// return -1;
			}
			continue;
		}
		Var var = li_vars + index;
		delete_a_var(var);
		// if (rtc < 0)
		// 	return rtc;
		list_remove_at(lily_ui.vars, index);
	}
	if (user_do)
		lily_out("done\n");
	return n;
}

// resize all used list
int32_t cmd_reduction_memory(int32_t n, char **arg)
{
	char tx[TX_len];
	Li_List *lists;
	int32_t i, j = 3;
	lists = &(lily_ui.cmds);

	if (n > 1 && str_index(arg[1], 'v')) // except vars
	{
		j = 2;
	}
	n = 0;
	for (i = 0; i < j; i++)
	{
		n += list_reduction(lists[i]);
	}
	event_broadcast_from_tag("memReduction", NULL);
	sprintf(tx, "free %d bytes\n", n);
	lily_out(tx);
	return n;
}
int32_t cmd_whos(int32_t nargin, char **arg)
{
	char tx[TX_len];
	char hit = 0;
	int32_t now;
	for (now = 1; now < nargin; now++)
	{
		hit = 0;
		char *item = NULL;
		item = arg[now];

		Var q = li_vars;
		int32_t n = lily_ui.vars->count;
		int32_t i;
		for (i = 0; i < n; i++)
		{
			if (str_equal(item, q[i].name))
			{
				if (q[i].isa)
					sprintf(tx, ">%s type:%c%c[%d]\n", q[i].name, q[i].isb ? '_' : ' ', q[i].type, q[i].len);
				else
					sprintf(tx, ">%s type:%c%c\n", q[i].name, q[i].isb ? '_' : ' ', q[i].type);
				lily_out(tx);
				hit = 1;
				break;
			}
		}
		if (hit)
			continue;
		lily_out(">not found:");
		lily_out(item);
		lily_out("\n");
		return -1;
	}

	if (nargin > 1)
		return nargin - 1;

	Var q = li_vars;
	int32_t n = lily_ui.vars->count;

	int32_t i;
	for (i = 0; i < n; i++)
	{
		sprintf(tx, "%s=", q[i].name);
		lily_out(tx);
		_var_to_string(tx, q + i, 0);

		lily_out(tx);
		lily_out("\n");
	}
	return i;
}
int32_t cmd_delay(int32_t n, char **arg)
{
	if (n == 1)
	{
		lily_ui.open_time = _li_millis() + 1000;
		return 1000;
	}
	else if (n == 2)
	{
		int32_t t = atoi(arg[1]);
		if (t <= 0)
		{
			li_error("neg arg", -__LINE__);
		}
		// return -1;
		lily_ui.open_time = _li_millis() + t;
		return t;
	}
	return -2;
}

int32_t cmd_echo(int32_t n, char **arg)
{
	char tx[TX_len];
	int32_t now = 1;
	int32_t index, ind = -1;
	char print_line = 1;
	char no_space_for_this_item = 0;
	for (; now < n; now++)
	{
		index = str_index(arg[now], '{');
		if (index >= 0)
		{
			arg[now][index++] = '\0';
			ind = str_index(arg[now] + index, '}');
			if (ind >= 0)
			{
				arg[now][index + ind] = '\0';
				ind += index + 1;
			}
			lily_out(arg[now]);
			int32_t at = _ebrace_value_from_string(arg[now] + index);
			if (at < 0)
				at = 0;
			index = search_var_in_Lily_ui(arg[now] + index);
			if (index < 0)
			{
				li_error("var missing", -__LINE__);
			}
			if (li_vars[index].type == 's')
			{
				if (li_vars[index].isa)
				{
					lily_out(li_vars[index].val.ps[at]);
				}
				else
				{
					lily_out(li_vars[index].val.vs);
				}
			}
			else
			{
				_var_to_string(tx, li_vars + index, at);
				lily_out(tx);
			}
			if (ind >= 0)
			{
				lily_out(arg[now] + ind);
			}
		}
		else if (arg[now][0] == '-')
		{
			const char *replace_key = "nRNtbasSdlrq-";
			// new line, no line, tab, back, alert, space, no space, $, left brace{, }, quote", minus and following str
			const char *replace_val = "\n\r#\t\b\a##${}\"#";
			int32_t ind = str_index(replace_key, arg[now][1]);

			if (ind < 0) // no match
			{
				lily_out(arg[now]);
			}
			else
			{
				if (replace_val[ind] == '#')
				{
					switch (arg[now][1])
					{
					case 'N':
						print_line = 0;
						continue;
						break;
					case 's':
						if (isD(arg[now][2]))
						{
							int32_t n = arg[now][2] - '0';
							for (; n; n--)
							{
								lily_out(" ");
							}
						}
						else
							lily_out(" ");
						continue;
						//						break;
					case 'S': // no blank for next item
						no_space_for_this_item = 1;
						continue;
					case '-':
						lily_out(arg[now] + 1);
						continue;
					default:
						lily_out(arg[now]);
					}
				}
				else
				{
					char val[2] = {'\0', '\0'};
					val[0] = replace_val[ind];
					lily_out(val);
					continue;
				}
			}
		}
		else
		{
			lily_out(arg[now]);
		}

		if (now < n - 1)
		{
			if (!no_space_for_this_item)
				lily_out(" ");
			else
				no_space_for_this_item = 0;
		}
		else if (print_line)
			lily_out("\n");
	}

	return n;
}

int32_t sleep_timer()
{
	if (_stack_top > 0)
		_stack_top--;
	if (have_listener)
	{
		tp_addTask(current_listener); // notify listener
	}
	return 0;
}
int32_t hijack_sleep(int32_t n, char **arg)
{
	return KeepHijack | Lily_cmd_do_not_notify;
}
int32_t cmd_hijack_sleep(int32_t n, char **arg)
{
	float sleep_time = 1.0f;
	if (n < 1)
	{
		li_error("bad arg", -1);
	}
	if (n >= 2)
	{
		sleep_time = _value_from_string_or_var(arg[1]);
		if (isnan(sleep_time))
		{
			li_error("field miss", -2);
		}
	}

	int32_t ticks = Second(sleep_time);
	if (ticks < 1)
		ticks = 1;
	li_new_timer_once(sleep_timer, ticks);
	return Lily_cmd_do_not_notify;
}

// @deprecated
int32_t cmd_type(int32_t n, char *arg[]) // note there's some not right
{
	if (n < 2)
	{
		return 0;
	}
	int32_t i, j;
	for (i = 1; i < n; i++)
	{
		char *s = arg[i];
		for (j = 0; s[j] != '\0'; j++)
		{
			lily_in(s[j]);
			if ((unsigned char)(ri + 1) == hi) // cmd queue full
			{
				lily_out("[warning]:cmd queue full, the following data lost\n");
				return 0;
			}
		}
		if (i + 1 < n)
			lily_in(' ');
	}
	return n;
}

// only for str var

// str-grep var str2, word count in the var
// str-len var, return the length of str
// str-split var str2 dstVar, split a str in to a array, the result saved at dstVar return the count of array
// str-find var str, find a sub-str in var
// str-rple var fromStr toStr, replace fromStr in var to toStr
// str-cut var formIndex length, str cut
// str-starts
int32_t str_collection(int32_t n, char *_arg[])
{
	char tx[TX_len];
	// str cmd = arg[0] + 4; // str-xxx
	str cmd = _arg[1];
	char **arg = _arg + 1;
	n--;

	assert_true(n > 1);
	assert_true(*cmd);
	int32_t src_index = search_var_in_Lily_ui(arg[1]);
	assert_msg(src_index >= 0, "var not found");
	Var svar = li_vars + src_index;
	int32_t at = -1;
	assert_msg(svar->type == 's', "var is not a str");

	str src;
	str *head;
	if (svar->isa)
	{
		at = _ebrace_value_from_string(arg[1]);
		if (at < -1) // bad expression
		{
			li_error("index must be a intergal", -__LINE__);
		}
		if (at == -1)
			at = 0;
		src = svar->val.ps[at];
		head = svar->val.ps + at;
	}
	else
	{
		head = &(svar->val.vs);
		src = svar->val.vs;
	}

	assert_not_empty(src);

	const char *replace_key = "nsqtae";
	// new line, no line, tab, back, alert, space, no space, $, left brace{, }, quote", minus and following str
	const str replace_val[] = {"\n", " ", "\"", "\t", "\a", ""}; //"\n\t\a ${}\"";

	if (cmd[0] == 'l' && n == 2) // len
	{
		int32_t n = strlen(src);
		sprintf(tx, "%d\n", n);
		lily_out(tx);
		return n;
	}
	if (cmd[0] == 'L' && n == 2) // show
	{
		int32_t lineIndex;
		int32_t i = 0;
		while (1)
		{
			lineIndex = str_index(src, '\n');
			if (lineIndex < 0)
			{
				return i;
			}
			src[lineIndex] = '\0';
			sprintf(tx, "%d:", i++);
			lily_out(tx);
			lily_out(src);
			lily_out("\n");
			src[lineIndex] = '\n';
			src += lineIndex + 1;
		}
	}
	assert_true(n > 2);
	str fstr = arg[2];

	if (*fstr == '-')
	{
		int32_t ind = str_index(replace_key, fstr[1]);
		if (ind >= 0 && fstr[2] == '\0')
		{
			fstr = replace_val[ind];
		}
	}
	if (cmd[0] == 's') // split: str-s var delimiter(char) dstVar
	{
		assert_msg(n > 3, "str-s var delimiter dstVar");
		char delimter = fstr[0];
		Li_List tokens = str_split(src, delimter);
		int32_t n = tokens->count;
		str *ss = list_content(tokens, str);

		if (n <= 1)
		{
			lily_out("no split\n");
			delete_list(tokens);
			return 0;
		}

		int32_t dst_var_ind = search_var_in_Lily_ui(arg[3]);

		if (dst_var_ind < 0)
		{
			dst_var_ind = public_new_vars_array(arg[3], 'S', NULL, n);
			// assert_true(dst_var_ind > 0);
		}
		Var dstVar = li_vars + dst_var_ind;

		if (dstVar->type != 's' || !dstVar->isa || dstVar->len < n)
		{
			delete_list(tokens);
			li_error("var error", -__LINE__);
		}
		int32_t i;
		for (i = 0; i < n; i++)
		{
			_assign_var_from_string(dstVar, ss[i], i);
		}
		sprintf(tx, "%d\n", n);
		lily_out(tx);

		delete_list(tokens);
		return n;
	}

	if (cmd[0] == 'g' || cmd[0] == 'f' || cmd[0] == 'i') // count find
	{
		Li_List indexes = str_count(src, fstr);
		if (indexes == NULL)
		{
			if (cmd[0] == 'i') // index
			{
				lily_out("-1\n");
			}
			else
				lily_out("0\n");
			return 0;
		}
		int32_t n = indexes->count;
		int32_t *ins = list_content(indexes, int32_t);

		if (cmd[0] == 'i') // index
		{
			n = ins[0];
			sprintf(tx, "%d\n", n);
			lily_out(tx);
			delete_list(indexes);
			return n;
		}
		int32_t i;
		sprintf(tx, "%d\n", n);
		lily_out(tx);
		if (cmd[0] == 'f') // find
		{
			i = 0;
			// for '\n' to end
			str s = src + ins[i] + 1;
			int32_t nli;
			if (cmd[1])
			{
				nli = str_index(s, '\n');
				if (nli > 0)
				{
					s[nli] = '\0';
				}
				/*int32_t len = strlen(s);
				if (len > 32)
				{
					s[31] = '\0';
				}*/
				lily_out(s - 1);
				lily_out("\n");
				if (nli > 0)
				{
					s[nli] = '\n';
				}
			}
			else
			{
				lily_out(s - 1);
				lily_out("\n");
			}

			/*if (len > 32)
			{
				s[31] = '\0';
			}*/

			for (i++; i < n; i++)
			{
				if (ins[i] == ins[i - 1] + 1) // like find ' ' in "hello  abc def"
					continue;
				s = src + ins[i] + 1;

				if (cmd[1])
				{

					nli = str_index(s, '\n');
					if (nli > 0)
					{
						s[nli] = '\0';
					}
					lily_out(s - 1);
					lily_out("\n");
					if (nli > 0)
					{
						s[nli] = '\n';
					}
				}
				else
				{
					lily_out(s - 1);
					lily_out("\n");
				}
			}
		}
		delete_list(indexes);
		return n;
	}

	if (cmd[0] == 'r') // replace
	{
		assert_msg(n > 3, "str-r var from to");

		if (*(arg[3]) == '-')
		{
			int32_t ind = str_index(replace_key, arg[3][1]);
			if (ind > 0 && arg[3][2] == '\0')
			{
				arg[3] = replace_val[ind];
			}
		}

		int32_t n = str_count_n(src, fstr);
		if (n == 0)
		{
			lily_out("no replace\n");
			return 0;
		}
		str newString = strReplace(src, fstr, arg[3]);
		assert_not_empty(newString);
		free(src);

		*head = newString;
		lily_out(newString);
		lily_out("\n");
		return n;
	}

	if (cmd[0] == 'c') // cut: str-c var from [len]
	{
		if (!str_is_numeric(fstr))
		{
			li_error("str-c var fromNum lenNum", -1822);
		}
		int32_t from, len = -1;

		if (n > 3)
		{
			if (str_is_numeric(arg[3]))
			{
				len = atoi(arg[3]);
			}
		}
		from = atoi(fstr);

		int32_t n = strlen(src);
		if (from < 0)
		{
			from = n + from; // -1 -> n-1
		}
		// note: no length check
		// assert_msg(from < n, "leng over", -1830);
		if (len > 0)
		{
			src[from + len] = '\0';
		}

		str news = new_string_by(src + from);
		free(src);

		*head = news;
		lily_out(news);
		lily_out("\n");
		if (len > 0)
		{
			return len;
		}
		return n - from;
	}

	if (cmd[0] == 't') // starts
	{
		if (str_startwith(src, fstr))
		{
			lily_out("true\n");
			return 1;
		}
		lily_out("false\n");
		return 0;
	}
	if (cmd[0] == 'w') // wrap: str-wrap delimeter
	{
		int32_t n = str_wrap(src, *fstr);
		lily_out(src);
		lily_out("\n");
		return n;
	}

	if (cmd[0] == 'e') // equal: str-equal var str
	{
		int32_t n = strcmp(src, fstr);
		if (n == 0)
		{
			lily_out("true\n");
			return 1;
		}
		lily_out("false\n");
		return 0;
	}

	if (cmd[0] == 'n') // toNumber var var1 var2 var3...
	{
		int32_t nn = 0;
		float *nums = get_nums_from_rx(src, &nn);

		int32_t i;
		for (i = 0; i < nn && i + 2 < n; i++)
		{
			str vn = arg[i + 2];
			sprintf(tx, "%s=%f", vn, nums[i]); // Note: not safe
			shell_do_var(tx);
		}
		return nn; // to int32_t
	}

	if (cmd[0] == 'L') // list: str-list var [line]
	{
		int32_t lineIndex;
		// list var from [lines]
		if (!str_is_numeric(fstr))
		{
			li_error("str-l var fromNum lenNum", -1822);
		}
		int32_t from, len = -1;

		if (n > 3)
		{
			if (str_is_numeric(arg[3]))
			{
				len = atoi(arg[3]);
			}
		}
		from = atoi(fstr);

		int32_t t = 0;
		for (t = 0; t < from; t++)
		{
			lineIndex = str_index(src, '\n');
			if (lineIndex < 0)
			{
				return 0;
			}
			src += lineIndex + 1;
		}

		if (len < 0)
		{
			len = 0xffff;
		}
		for (t = 0; t < len; t++)
		{
			lineIndex = str_index(src, '\n');
			if (lineIndex < 0)
			{
				return t;
			}
			src[lineIndex] = '\0';
			sprintf(tx, "%d:", t + from);
			lily_out(tx);
			lily_out(src);
			lily_out("\n");
			src[lineIndex] = '\n';
			src += lineIndex + 1;
		}
		return t;
	}

	li_error("mode not found", -__LINE__);
	return 0;
}

int32_t cmd_call(int32_t n, char *arg[])
{
	static Li_List newNames = NULL; // List<str>
	if (n == 1)						// remove unused names
	{
		if (!newNames)
			return 0;
		int32_t n = newNames->count;
		str *names = list_content(newNames, str);
		int32_t i;
		for (i = 0; i < n; i++)
		{
			int32_t still_in = search_cmd_in_Lily_ui(names[i]);
			if (still_in >= 0)
				continue;
			still_in = search_fun_in_Lily_ui(names[i]);
			if (still_in >= 0)
				continue;
			// not in, remove that
			free(names[i]);

			list_remove_at(newNames, i);
			i--;
			n--;
		}

		if (newNames->count == 0)
		{
			delete_list(newNames);
		}
		return n;
	}
	// call cmd newName
	if (n < 3)
	{
		li_error("bad arg, use call cmd newName", -2267);
	}
	int32_t index = search_cmd_in_Lily_ui(arg[1]);
	str *name_handle = NULL;
	uint32_t *hashCode_handle = NULL;
	if (index >= 0)
	{
		name_handle = &(li_cmds[index].name);
		hashCode_handle = &(li_cmds[index].hashCode);
	}
	else
	{
		index = search_fun_in_Lily_ui(arg[1]);
		if (index >= 0)
		{
			name_handle = &(li_funs[index].name);
			hashCode_handle = &(li_funs[index].hashCode);
		}
	}
	if (!name_handle)
	{
		li_error("not found", -__LINE__);
	}
	// if(arg[1])
	str name = new_string_by(arg[2]);
	assert_not_empty(name);
	*name_handle = name;
	*hashCode_handle = getHashCodeS(name);

	if (newNames == NULL)
	{
		newNames = new_li_cap(str, 2);
	}
	else
	{
		//		str *ns = list_content(newNames, str);
		int32_t at = list_find(newNames, arg[1]);
		if (at >= 0)
		{
			free(arg[1]);

			list_remove_at(newNames, at);
		}
	}
	li_add(newNames, name);
	list_reduction(newNames);
	return 1;
}

// wait key for n times
int32_t cmd_hijacker_wait_key(int32_t n, char **s)
{
	static int32_t in_hijack = 0;
	static unsigned char ri_b, rh_b;
	static int32_t cmd_queue_in_waitting_b, busy_b;
	char tx[TX_len];
	static Li_List assign_vars = NULL; // List<str>
	// static Tasks_def hooks_copy = NULL;
	if (in_hijack == 0)
	{
		int32_t i;
		for (i = 1; i < n; i++)
		{
			str sx = new_string_by(s[i]);
			if (assign_vars == NULL)
			{
				assign_vars = new_list(sizeof(str), 1);
				assert_not_empty(assign_vars);
			}
			li_add(assign_vars, sx);
		}

		if (n > 1)
		{
			in_hijack = n - 1; // atoi(s[1]);
		}
		if (in_hijack < 1)
			in_hijack = 1;
		add_hijack(cmd_hijacker_wait_key);
		// backup cmd queue
		ri_b = ri;
		rh_b = hi;
		busy_b = _lily_is_busy;
		cmd_queue_in_waitting_b = _cmd_queue_in_waiting;
		// free cmd queue
		// note: make sure all the data are received so that the following data in-stream are from user
		// and make sure only one cmd are got once in a loop to avoid re-skip
		hi = ri;	// skip the following cmds
		if (busy_b) // called by loop/for
		{
			_cmd_queue_in_waiting = 0;
		}
		else // called from cmd_queue_exced, so this function itself is a cmd need to  deal
			_cmd_queue_in_waiting = 1;
		_lily_is_busy = 0;

		if (assign_vars)
		{
			lily_out(*(list_content(assign_vars, str)));
		}
		else
			lily_out("K");
		lily_out(">");
		return Lily_cmd_do_not_notify; // first enter, not notify listener
	}
	else
	{
		// this partition is call only by cmd_queue_exced
		in_hijack--;
		lily_out(s[0]);
		if (assign_vars != NULL)
		{
			str *ss = list_content(assign_vars, str);
			str sx = *ss;
			sprintf(tx, "%s=%s", sx, s[0]);
			Pipe back = lily_get_current_pipe();
			lily_redirect_output(NULL);
			shell_do_var(tx);
			lily_redirect_output(back);

			list_remove_at(assign_vars, 0);
			if (assign_vars->count == 0)
			{
				delete_list(assign_vars);
				assign_vars = NULL;
			}
		}
		if (in_hijack)
		{
			lily_out("\n"); // K>
			if (assign_vars)
			{
				lily_out(*(list_content(assign_vars, str)));
			}
			else
				lily_out("K");
			lily_out(">");

			return KeepHijack | Lily_cmd_do_not_notify; // still keep hijack,but not notify
		}
		_lily_is_busy = busy_b;
		ri = ri_b;
		hi = rh_b;
		if (_lily_is_busy)
			_cmd_queue_in_waiting = cmd_queue_in_waitting_b + 1;
		else
			_cmd_queue_in_waiting = cmd_queue_in_waitting_b;
		if (_cmd_queue_in_waiting > 1)
			tp_addTask(_get_cmd_from_rx);
		lily_out("<\n");
		// lily_hooks_cmd_done = hooks_copy;
		// if (hooks_copy != NULL)
		// 	addTask_(hooks_copy);

		in_hijack = 0;
		return 0; // free hijack, work done, notify
	}
}

void utility_init()
{
#define new_cmd(name, fun, help)                \
	{                                            \
		public_a_cmd_link_doc(#name, fun, help); \
	}
	new_cmd(delay, cmd_delay, "delayCmd:delay [ms]");
	new_cmd(wk, cmd_hijacker_wait_key, "waitKey\nwk [assignVarsList]");
	new_cmd(sleep, cmd_hijack_sleep, "sleepSec\nsleep second");
	new_cmd(echo, cmd_echo, "echo to screen\necho txt={var}-n txt\n"
							 "special char:\n-n:new line\n-R:\\r\n-N:no new line\n-t:tab\n"
							 "-b:backspace\n-a:alert\n-s[n]:space\n-S:no space\n-d:$\n-l:{\n-r:}\n-q:\"\n--:-");
	// new_cmd(type, cmd_type, "input str\ntype abcd");
	new_cmd(call, cmd_call, "rename cmd\ncall [cmd newName]");
	new_cmd(str, str_collection, "string operation collection\nstr-grep var str\n -len var\n"
								  " -split var delimeter dstArray\n -find var str\n -replace var from to\n -cut from [len]\n"
								  " -test var starts\n -wrap var delimeter\n -equal var str\n -index var str\n"
								  " -num var [dstVarsList]\n -List var [from] [lines]");
#undef new_cmd
}